/* Copyright (c) 2010, Michael J. Allison

 Redistribution and use in source and binary forms, with or without modification, 
 are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, 
 this list of conditions and the following disclaimer.
 
 * Redistributions in binary form must reproduce the above copyright notice, 
 this list of conditions and the following disclaimer in the documentation 
 and/or other materials provided with the distribution.
 
 * Neither the name of The Storage Networking Industry Association (SNIA) nor 
 the names of its contributors may be used to endorse or promote products 
 derived from this software without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
 THE POSSIBILITY OF SUCH DAMAGE.
 */

package xamcommons.objectinspector;

import java.util.Enumeration;
import java.util.Vector;
import java.util.prefs.Preferences;

import java.awt.Color;
import java.awt.Rectangle;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;

import org.snia.xam.FieldContainer;
import org.snia.xam.XAMException;
import org.snia.xam.XAMLibrary;
import org.snia.xam.XStream;
import org.snia.xam.XSystem;
import org.snia.xam.XSet;

import org.snia.xam.util.SASLUtils;
import org.snia.xam.util.XAMLibraryFactory;

/**
 * Main class for the ObjectInspector tool. Extending the Swing JFrame the
 * tool displays various XAM objects as tables embeded in a JTabbedDisplay. 
 * The only way to adjust what types of objects are displayed is to rewrite 
 * the code in this class. 
 * 
 * In the future it would be nice to allow the user to enter a query in 
 * order to find other XSets and inspect those. Another option would be to 
 * allow the user to enter a XUID and retrieve that XSet for display.
 *
 * This class also saves any XRI values used in the Java persistent preferences
 * object. When the application exits, the window geometry is also saved.
 */
public class ObjectInspector
   extends JFrame
   implements WindowListener
{
   static final String TITLE = "XAM Object Inspector 1.0";
   static final String XRI = "snia-xam://ReferenceVIM!localhost";
   
   static final String XAM_LIBRARY             = "XAM-Library";
   static final String UNAUTHENTICATED_XSYSTEM = "Unathenticated-XSystem";
   static final String AUTHENTICATED_XSYSTEM   = "Authenticated-XSystem";
   static final String UNCOMMITTED_XSET        = "Uncommitted-XSet";
   static final String COMMITTED_XSET          = "Committed-XSet";
   static final String QUERY_XSET              = "Query-XSet";
   
   static final String XRI_KEY                 = "ObjectInspector.xri.list.";
   static final String GEOM_KEY                = "ObjectInspector.window.geometry.";
   static final String KEY_LAST_USER           = "ObjectInspector.last.user";
   
   final static Object s_exit_lock = new Object();
   
   JTabbedPane m_tabbed_pane = new JTabbedPane();
   Vector<String> m_xri_list = new Vector<String>();
   
   /**
    * Produce a JScrollPane with the contents of the fields in the 
    * FieldContainer. The main program will place the JScrollPane in a new
    * tab.
    * @param className The name of the type of FieldContainer being displayed. This name will be used to retrieve a CanonicalClass of the same name.
    * @param fc The FieldContainer to display.
    *
    */
   public JScrollPane dump( String className, FieldContainer fc )
      throws XAMException
   {
      FieldContainerDataModel fcdm = 
         new FieldContainerDataModel( className, fc );

      JTable table = new JTable( fcdm );
      table.setShowGrid(true);
      table.setGridColor( Color.black );
      table.setDefaultRenderer(Object.class, new CellStausRenderer());
      return new JScrollPane(table);
   }
   
   /**
    * Translate the Canonical Class name for display. If the CanonicalClass
    * did not load correctly, the name is offset by asterik characters.
    */
   public String name( String inName )
   {
      CanonicalClass cc = CanonicalClass.GetClass( inName );
      if( cc.isLoadedCorrectly() )
         return inName;
      else 
         return "*" + inName + "*";
   }

   /**
    * Submit a query and wait for it to be completed. 
    * Return the query XSet. 
    */
   XSet query( XSystem sys ) throws XAMException
   {
      XSet qXSet = sys.createXSet( XSet.MODE_UNRESTRICTED );
      qXSet.createProperty( XSet.XAM_JOB_COMMAND, false,
                            XSet.XAM_JOB_QUERY );
      XStream strm = qXSet.createXStream( XSet.XAM_JOB_QUERY_COMMAND, 
                                          false, 
                                          XAMLibrary.TEXT_PLAIN_MIME_TYPE + 
                                          ";charset=utf-8" );
      try { strm.write( "select \".xset.xuid\"".getBytes( "utf-8" ) ); }
      catch( Exception e )
      {
         throw new XAMException("Unabled to write query string", e);
      }
      strm.close();
      
      qXSet.submitJob();

      while( true )
      {
         String status = qXSet.getString( XSet.XAM_JOB_STATUS );
         if( status.equals( XSet.XAM_JOB_STATUS_COMPLETE ) )
            break;
         try{ Thread.sleep( 100 ); }
         catch( InterruptedException ie ) { ; }
      }

      return qXSet;
   }
   
   /**
    * Create the main ObjectInspect, load the XRIs from the preferences.
    */
   public ObjectInspector(XAMLibrary xam)
      throws XAMException
   {
      Preferences prefs = Preferences.userNodeForPackage(this.getClass());
      
      loadXRIList( prefs );
      loadWindowGeometry( prefs );
      
      setTitle( TITLE );
      addWindowListener(this);    
      
      setVisible(true);
      
      if( ! m_xri_list.contains(XRI))
         m_xri_list.add(XRI);
      
      String xri = null;
      XRIDialog xd = null;
      while( true )
      {
         xd = new XRIDialog( this, m_xri_list );
         xri = xd.getXRI();
         
         if( xd.isOK() )
         {
            setTitle( xri );
            saveXRIs();
            break;
         }
      }
      
      this.getContentPane().add(m_tabbed_pane);
      m_tabbed_pane.add( name(XAM_LIBRARY), dump(XAM_LIBRARY, xam) );
      
      XSystem sys = xam.connect(xri);
      m_tabbed_pane.add( name(UNAUTHENTICATED_XSYSTEM), 
                         dump( UNAUTHENTICATED_XSYSTEM, sys));
      
      if( xd.isPlain() )
      {
         PlainCredentialsDialog pcd = 
            new PlainCredentialsDialog( this, 
                                        prefs.get( KEY_LAST_USER, "" ));
         if( pcd.isOK() )
         {
            prefs.put( KEY_LAST_USER, pcd.getUsername() );
            SASLUtils.authenticatePlain( sys, pcd.getUsername(), 
                                         pcd.getPassword() );
         }
         else
            SASLUtils.authenticateAnonymous( sys, "" );
      }


      m_tabbed_pane.add( name(AUTHENTICATED_XSYSTEM), 
                         dump(AUTHENTICATED_XSYSTEM, sys));
         
      XSet newXSet = sys.createXSet( XSet.MODE_UNRESTRICTED );
      m_tabbed_pane.add( name(UNCOMMITTED_XSET), 
                         dump(UNCOMMITTED_XSET, newXSet) );
         
      XSet committedXSet = sys.createXSet(XSet.MODE_UNRESTRICTED);
      committedXSet.commit();
      m_tabbed_pane.add( name(COMMITTED_XSET), 
                         dump(COMMITTED_XSET, committedXSet) );

      XSet queryXSet = query( sys );
      m_tabbed_pane.add( name(QUERY_XSET),
                         dump(QUERY_XSET, queryXSet));
   }
   
   /**
    * Load the XRI list from the preferences.
    */
   private void loadXRIList( Preferences prefs )
   {
      try
      {
         String keys[] = prefs.keys();
         
         for( String key : keys )
         {
            if( key.startsWith(XRI_KEY) )
            {
               String value = prefs.get(key, "");
               if( ! m_xri_list.contains(value) )
                  m_xri_list.add(value);
            }
         }
      }
      catch( Exception e )
      {
         System.out.println( "Can't get list or XRIs because of exception: " + 
                             e );
      }
   }
   
   /**
    * Save the XRI list to the preferences so they can be loaded into the XRI
    * selection dialog next time. 
    */
   private void saveXRIs()
   {
      Enumeration<String> e = m_xri_list.elements();
      Preferences prefs = Preferences.userNodeForPackage(this.getClass());
      
      // Clear the old stuff from prefs
      try
      {
         String keys[] = prefs.keys();
         for( String key : keys )
         {
            prefs.remove(key);
         }
      }
      catch( Exception ex )
      {
         // ignore
      }
      
      int i = 0;
      
      while( e.hasMoreElements() )
      {
         String xri = e.nextElement();
         String key = XRI_KEY + "list." + i++;
         prefs.put(key, xri);
      }
   }
   
   /**
    * Save the window size and position for next run.
    */
   private void saveWindowGeometry()
   {
      Preferences prefs = Preferences.userNodeForPackage(this.getClass());
      Rectangle bounds = getBounds();
      prefs.putInt( GEOM_KEY + "x", (int) bounds.getX() );
      prefs.putInt( GEOM_KEY + "y", (int) bounds.getY() );
      prefs.putInt( GEOM_KEY + "width",  (int) bounds.getWidth() );
      prefs.putInt( GEOM_KEY + "height", (int) bounds.getHeight() );
   }
   
   /**    
    * Load and set the window geometry. 
    */
   private void loadWindowGeometry( Preferences prefs )
   {
      int x = prefs.getInt( GEOM_KEY + "x", 10 );
      int y = prefs.getInt( GEOM_KEY + "y", 10 );
      int w = prefs.getInt( GEOM_KEY + "width", 640 );
      int h = prefs.getInt( GEOM_KEY + "height", 480 );
      
      setLocation( x, y );
      setSize( w, h );
   }
   
   /**
    * @param args
    */
   public static void main(String[] args)
      throws XAMException
   {
      ObjectInspector oi = null;
      try
      {
         XAMLibrary xam = XAMLibraryFactory.newXAMLibrary();
         
         oi = new ObjectInspector(xam);
      }
      catch( Exception e )
      {
         e.printStackTrace();
      }
      finally 
      {
         synchronized( s_exit_lock ) 
         { 
            try
            {
               s_exit_lock.wait();
            }
            catch (InterruptedException e)
            {
               // Ignore
            } 
         }
      }
   }
   
   // Window Listener methods.

   public void windowActivated(WindowEvent e)
   {
   }
   
   public void windowClosed(WindowEvent e)
   {
      synchronized( s_exit_lock ) { s_exit_lock.notifyAll(); }
      saveWindowGeometry();
      System.exit(0);
   }
   
   public void windowClosing(WindowEvent e)
   {
      synchronized( s_exit_lock ) { s_exit_lock.notifyAll(); }
      saveWindowGeometry();
      System.exit(0);
   }
   
   public void windowDeactivated(WindowEvent e)
   {
   }
   
   public void windowDeiconified(WindowEvent e)
   {
   }
   
   public void windowIconified(WindowEvent e)
   {
   }
   
   public void windowOpened(WindowEvent e)
   {
   }
}
